package cn.fx.framework.redis;

import cn.fx.framework.utils.New;
import cn.fx.framework.utils.StringUtils;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.Cache;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 自定义RedisCache管理器
 * 1、配置序列化处理类：StringRedisSerializer为String、GenericFastJsonRedisSerializer为Object。
 * 2、整理Redis缓存配置Map(缓存名称，缓存配置对象),为建立多个缓存做准备。配置对象主要是[名称与前缀]，如名称"dict",前缀"默认前缀:dict:"。
 * 3、
 * @author fxcn
 * @date 2024-9-26
 */
public class MyRedisCacheManager extends RedisCacheManager implements ApplicationContextAware, InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(MyRedisCacheManager.class);

    //允许本地缓冲的Cache名称集合Set
    public static final Set<String> ENABLE_LOCAL_CACHE_SET = New.set();

    private ApplicationContext applicationContext;

    //Redis缓存配置Map(缓存名称，缓存配置对象),为建立多个缓存做准备
    private Map<String, RedisCacheConfiguration> initialCacheConfigurationMap;

    /**
     * key serializer
     */
    public static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();

    /**
     * value serializer
     * <pre>
     *     使用 FastJsonRedisSerializer 会报错：java.lang.ClassCastException
     *     FastJsonRedisSerializer<Object> fastSerializer = new FastJsonRedisSerializer<>(Object.class);
     * </pre>
     */
    public static final GenericFastJsonRedisSerializer FASTJSON_SERIALIZER = new GenericFastJsonRedisSerializer();

    /**
     * key serializer pair
     */
    public static final RedisSerializationContext.SerializationPair<String> STRING_PAIR = RedisSerializationContext
            .SerializationPair.fromSerializer(STRING_SERIALIZER);

    /**
     * value serializer pair
     */
    public static final RedisSerializationContext.SerializationPair<Object> FASTJSON_PAIR = RedisSerializationContext
            .SerializationPair.fromSerializer(FASTJSON_SERIALIZER);

    public MyRedisCacheManager(RedisCacheWriter cacheWriter,
                               RedisCacheConfiguration defaultCacheConfiguration,
                               Map<String, RedisCacheConfiguration> cacheConfigurationMap) {
        super(cacheWriter, defaultCacheConfiguration);
        this.initialCacheConfigurationMap = cacheConfigurationMap;
    }

    /**
     * Redis缓存集合CacheMap
     */
    private final ConcurrentMap<String, RedisCacheWrapper> cacheMap = new ConcurrentHashMap(16);

    @Override
    public Cache getCache(String cacheName) {
        RedisCacheWrapper cache = cacheMap.get(cacheName);
        if(null == cache){
            Cache rawCache = super.getCache(cacheName);
            cache = new RedisCacheWrapper(this,rawCache);
            log.info("redis缓存{}由RedisCacheWrapper包装。",cacheName);
            this.cacheMap.put(cacheName,cache);
        }
        return cache;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

/*    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
    }*/

    @Override
    protected Collection<RedisCache> loadCaches() {
        List<RedisCache> caches = New.list();
        if(null != initialCacheConfigurationMap && initialCacheConfigurationMap.size() > 0) {
            for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfigurationMap.entrySet()) {
                caches.add(super.createRedisCache(entry.getKey(), entry.getValue()));
            }
            log.info("加载自定义缓存完成，共加载了{}个", initialCacheConfigurationMap.size());
        }
        return caches;
    }

    /**
     * 构造redis缓存管理器
     * @param redisConnectionFactory redis连接工厂
     * @param defaultPrefixKeysWith 缓存默认前辍
     * @param defaultCacheEntryTTL 缓存默认有效期，0则永久有效
     * @param cacheInitialHandler 缓存初始化
     * @return
     */
    public static MyRedisCacheManager buildCacheManager(RedisConnectionFactory redisConnectionFactory,
                                                        String defaultPrefixKeysWith,
                                                        int defaultCacheEntryTTL,
                                                        String cacheInitialHandler){
        if(StringUtils.isNullOrEmpty(defaultPrefixKeysWith)){
            defaultPrefixKeysWith = "cache:";
        }else if(!defaultPrefixKeysWith.endsWith(":")){
            defaultPrefixKeysWith += ":";
        }
        // 初始化一个RedisCacheWriter
        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //配置一个默认缓存配置，key使用字符串，值使用fastjson
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                //若创建一个缓存时没有指定前辍，则使用这个前辍
                //.prefixCacheNameWith(defaultPrefixKeysWith)
                //若值为空的不缓存
                .disableCachingNullValues()
                // 使用注解时的序列化、反序列化
                .serializeKeysWith(MyRedisCacheManager.STRING_PAIR)
                .serializeValuesWith(MyRedisCacheManager.FASTJSON_PAIR);
        if(defaultCacheEntryTTL > 0){
            defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofMinutes(defaultCacheEntryTTL));
        }
        Map<String, RedisCacheConfiguration> cacheConfigurationMap = null;
        if(StringUtils.isNotEmpty(cacheInitialHandler)){
            try {
                Object instance = Class.forName(cacheInitialHandler).newInstance();
                if(instance instanceof RedisInitialCacheHandler){
                    cacheConfigurationMap = ((RedisInitialCacheHandler)instance).customCaches(defaultPrefixKeysWith);
                }
            }catch (Exception e){
                throw new Error("请定义正确的redis缓存配置，应该实现cn.joyi.framework.redis.RedisInitialCacheHandler接口。");
            }
        }
        if(cacheConfigurationMap == null){
            cacheConfigurationMap = New.map();
        }
        cacheConfigurationMap.put("user", MyRedisCacheManager.defaultCacheConfig(defaultPrefixKeysWith));
        cacheConfigurationMap.put("org", MyRedisCacheManager.defaultCacheConfig(defaultPrefixKeysWith));
        cacheConfigurationMap.put("dict", MyRedisCacheManager.defaultCacheConfig(defaultPrefixKeysWith));
        cacheConfigurationMap.put("sys", MyRedisCacheManager.defaultCacheConfig(defaultPrefixKeysWith));
        return new MyRedisCacheManager(cacheWriter, defaultCacheConfig, cacheConfigurationMap);
    }

    /**
     * 生成一个默认的缓存配置项
     * @param keyWith 缓存以这个开头
     * @return
     */
    public static RedisCacheConfiguration defaultCacheConfig(String keyWith) {
        if(StringUtils.isNullOrEmpty(keyWith)){
            keyWith = "starter:";
        }else if(!keyWith.endsWith(":")){
            keyWith += ":";
        }
        RedisCacheConfiguration rcc = RedisCacheConfiguration.defaultCacheConfig();
        //设置默认的key及value的序列化方案
        rcc = rcc.prefixCacheNameWith(keyWith)
                .disableCachingNullValues()
                .serializeKeysWith(MyRedisCacheManager.STRING_PAIR)
                .serializeValuesWith(MyRedisCacheManager.FASTJSON_PAIR);
        return rcc;
    }

    @Qualifier("redisTemplate")
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 检测redis缓存的key所在方法上是否启用了二级缓存
     * @param redisCacheKey
     * @return
     */
    public static boolean isEnableLocalCache(String redisCacheKey){
        if(StringUtils.isNotEmpty(redisCacheKey)){
            String key = redisCacheKey;
            int index = redisCacheKey.indexOf("#");
            if(index != -1){
                key = redisCacheKey.substring(0,index);
            }
            return ENABLE_LOCAL_CACHE_SET.contains(key);
        }
        return false;
    }
}
